#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>

#include "./keyboard/keyboard.h"

#define FIXED_COL 1//设置落定的颜色为红色
#define BC 7//背景颜色为灰色
#define W 10//背景宽度
#define H 20//背景长度

int  FC =4;//初始方块的颜色
int style=4;//初始方块的样式
long long SCORE;//得分

//保存一个方块的结构体
//一个方块的长和宽度都不大于5
struct shape {
	int s[5][5];
};

//用来标记方块的位置（）
struct data {
	int x;
	int y;
};
//初始化位置为3，0（第0 行，第3列）
struct data pos = {3,0};

//将背景盘限制大小
int background[H][W];

//将所有的方块样式都保存在二维数组中
struct shape shape_arr[7] = {
	{ 0,0,0,0,0, 0,0,1,0,0, 0,1,1,1,0, 0,0,0,0,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,0,1,0,0, 0,0,1,0,0, 0,0,1,0,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,0,1,0,0, 0,0,1,0,0, 0,0,1,1,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,0,1,0,0, 0,0,1,0,0, 0,1,1,0,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,1,1,0,0, 0,1,1,0,0, 0,0,0,0,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,1,1,0,0, 0,0,1,1,0, 0,0,0,0,0, 0,0,0,0,0},
	{ 0,0,0,0,0, 0,0,1,1,0, 0,1,1,0,0, 0,0,0,0,0, 0,0,0,0,0} 
};

//绘制每一个小正方形
void print_element(int x, int y, int c)
{
	x *= 2;//长度是因为一个小正方形占两个字符
	x++;
	y++;
  //使用VT100编程控制输出显示
	printf("\033[%d;%dH", y, x);//控制输出位置
	printf("\033[3%dm\033[4%dm", c, c);//控制小正方形的颜色
	printf("[]");//用两个字符来表示一个小正方形
	printf("\033[?25l");//隐藏光标
	printf("\033[0m");//将颜色恢复
	fflush(stdout);
}


//绘制背景，有效位置绘制成前景色，无效位置绘制为背景色
void print_bk(  )
{
  int i = 0;
  int j = 0;
	for (i = 0; i< H; i++) 
  {
		for (j = 0; j< W; j++) 
    {
			if ( background[i][j] == 0 )
				print_element(j, i, BC);//绘制为背景颜色
			else
				print_element(j, i, FC);//绘制为图形颜色
		}
	}
}

//绘制图形
void print_shape(int x, int y, struct shape p, int c)
{
  int i = 0;
  int j = 0;
	for (i = 0; i < 5; i++) 
  {
		for (j = 0; j < 5; j++) 
    {
			if (p.s[i][j] != 0 )
				print_element(x+j, y+i, c);
		}
	}
}
//设置背景,用于每次更新背景
void set_back(struct data *t, struct shape p)
{
  int i = 0;
  int j = 0;
	for (i = 0; i < 5; i++)
  {
		for (j = 0; j < 5; j++) 
    {
			if ( p.s[i][j] != 0 )
      {
				background[t->y + i][t->x + j] = 1;
      }
		}
	}
}
//限定图形移动的范围
int can_move(int x, int y, struct shape p)
{
  int i = 0;
  int j = 0;
  //注意此处判断应该用图形有效位置来判断
  //若是图形的边框越界是允许的
	for (i = 0; i < 5; i++) 
  {
		for (j = 0; j < 5; j++) 
    {
			if (p.s[i][j] == 0)//该位置无效
				continue;
			if ( y+i >= H )//如果超过下边界
				return 0;
			if ( x+j < 0 )//超过左边界
				return 0;
			if ( x+j >= W )//超过右边界
				return 0;
			if (background[y+i][x+j] != 0 )//该位置已经有效
				return 0;
		}
	}
	return 1;//其他情况可以移动
}

//实现是否可以消去一行
void clean_line()
{
	int i;
  int j = 0;
	int total;
  //从最上面一行来判断是否整行都满了
	for (i = 0; i < H; i++) 
  {
		total = 0;//记录该行的有效位置个数
		for (j = 0; j < W; j++) 
    {
			if ( background[i][j] != 0 )
				total++;
		}

    //如果该行可以进行消去
		if ( total == W ) 
    {
      int k = 0;
			for (k = i; k > 0; k--) 
      {
        //依次将改行上面的每一行都依次拷贝给下一行(依次覆盖),以此来实现消行
				memcpy(background[k], background[k-1], sizeof(background[k]));
			}
      //拷贝完成后，将最上面一行进行重绘为背景色
			memset(background[0], 0x00, sizeof(background[0]));
      print_bk();
      //修改用户成绩
      SCORE += 10;
	//printf("\033[%d;%dH", y, x);
	//printf("\033[3%dm\033[4%dm", c, c);
	    printf("\033[15;22H");//TODO
      printf("\033[33m ");
      printf("得分:[%ld]\n",SCORE);
	    printf("\033[0m");//将颜色恢复
		}
	}
}

//判断游戏是否结束
void over()
{
  int i = 0;
	int total = 0;
  //这里要根据设置的图形初始位置，来判断图形是否已经占满整个背景
	for (i = 0; i < W; i++)
  {
		if ( background[3][i] != 0 ) 
    {
	    printf("\033[33m ");
			printf("\033[17;22H");//TODO
			printf("挑战失败!\n");//TODO
			printf("\033[17;23H");//TODO
      printf("你的最终得分为：%ld\n",SCORE);
			printf("\033[?25h");//TODO
	    printf("\033[0m");//将颜色恢复

			recover_keyboard();//在游戏结束时，恢复键盘控制
			exit(0);
		}
	}
}

//实现图形的自动下落
void tetris_timer(struct data *p)
{
	print_shape(p->x, p->y, shape_arr[style], BC);
	if (can_move(p->x, p->y+1, shape_arr[style]) ) 
  {
    //如果还可以向下移动
		p->y++;
		print_shape(p->x, p->y, shape_arr[style], FC);
	} 
  else 
  {
    //已经不可以向下移动.即落定的时候判断是否可以消去一行，游戏是否结束
		set_back(p, shape_arr[style]);//更新背景
		print_shape(p->x, p->y, shape_arr[style], FC);
		clean_line();//再判断是否可以消去一行
		over();//再判断是否结束，注意，和上面判断消行顺序不可以变
    FC = FIXED_COL;//每次落定都让图形称为固定的颜色
		print_bk();
    FC=rand()%7;//随机数控制新图形的颜色,但图形颜色不能和背景重复
    if(FC==0) 
      FC=FC+1;
    style=rand()%7;//随机数控制新图形样式
		p->y = 1;
	}
}

//实现图形下落过程中的旋转变形
//右旋90°
struct shape trun_right_90(struct shape p)
{
	struct shape tmp;
  int i = 0;
  int j = 0;
	for (i = 0; i < 5; i++) 
  {
		for (j = 0; j < 5; j++) 
    {
			tmp.s[i][j] =  p.s[4-j][i];//将[x][y]-->[col - y][x] col为列的最大下标
		}
	}
	return tmp;
}
//左旋90°
struct shape trun_left_90(struct shape p)
{
	struct shape tmp;
  int i = 0;
  int j = 0;
	for (i = 0; i < 5; i++) 
  {
		for (j = 0; j < 5; j++) 
    {
			tmp.s[i][j] =  p.s[j][4-i];//将[x][y]-->[y][row - x] row为行的最大下标
		}
	}
	return tmp;
}

//上下左右按键触发
void tetris(struct data *p)
{
	int n = get_key();//获取键盘的输入
  //上键进行进行图形旋转
	if ( is_up(n) ) 
  {
		print_shape(p->x, p->y, shape_arr[style], BC);
    //先假设可以旋转，右旋90°
		shape_arr[style] = trun_right_90(shape_arr[style]);
    //再判断旋转后的位置是否合理
		if ( can_move(p->x, p->y, shape_arr[style]) == 0) 
    {
      //不合理的话就将需要转回去
			shape_arr[style] = trun_left_90(shape_arr[style]);
		}

    //合理的话就绘制旋转后的图形
		print_shape(p->x, p->y, shape_arr[style], FC);
	} 
  else 
  if ( is_left(n) ) 
  {
    //向左移动
		print_shape(p->x, p->y, shape_arr[style], BC);
    //判断左边是否还可以进行移动
		if ( can_move(p->x-1, p->y, shape_arr[style]) ) 
    {
			p->x--;
      //可以移动的话就进行移动更新
			print_shape(p->x, p->y, shape_arr[style], FC);
		} 
    else 
    {
			print_shape(p->x, p->y, shape_arr[style], FC);
		}
	}
	else 
    if ( is_right(n) ) 
    {
      //判断右边是否可以移动
		print_shape(p->x, p->y, shape_arr[style], BC);
		if ( can_move(p->x+1, p->y, shape_arr[style]) ) 
    {
			p->x++;
      //可以移动的话就进行移动更新
			print_shape(p->x, p->y, shape_arr[style], FC);
		} 
    else 
    {
			print_shape(p->x, p->y, shape_arr[style], FC);
		}
	}
	else 
    if ( is_down(n) ) 
    {
      //判断下面是否可以移动
		print_shape(p->x, p->y, shape_arr[style], BC);
		if ( can_move(p->x, p->y+1, shape_arr[style]) ) 
    {
			p->y++;
      //可以移动就进行移动更新
			print_shape(p->x, p->y, shape_arr[style], FC);
		} 
    else 
    {
			print_shape(p->x, p->y, shape_arr[style], FC);
		}
	}
}

//定时器来进行自动向下移动
void Handler(int s)
{
	tetris_timer(&pos);
}

//Ctrl+C 后处理结束游戏后恢复
void Handler_int(int s)
{
	printf("\033[?25h");//光标重现
	recover_keyboard();//键盘恢复
	exit(0);
}

int main( void )
{
  //定义定时器
  srand((unsigned) time(NULL));
	struct sigaction act;
	act.sa_handler = Handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
  //信号捕捉alarm
	sigaction(SIGALRM, &act, NULL);

  //捕获信号来进行处理中途终止游戏
  //keyboard
	signal(SIGINT, Handler_int);
	signal(SIGQUIT, SIG_IGN);

	struct itimerval it;
  //定时器初始值设置为1us
	it.it_value.tv_sec = 0;
	it.it_value.tv_usec = 1;
  //定时时间设为1s(即自动下落时间为1s一次)
	it.it_interval.tv_sec = 1;
	it.it_interval.tv_usec = 0;
  //注册闹钟
	setitimer(ITIMER_REAL, &it, NULL);

	init_keyboard();
	print_bk();
	while ( 1 ) {
		tetris(&pos);
	}
	recover_keyboard();
}

